通常關聯都是兩兩張資料表之間的關係,而多型態關聯則是打破這個限制讓一張表可以同時關連到兩張以上的資料表。
以官方的範例為例,假設我們有 user 跟 post 兩種資料,而他們各自有所屬的 image 資料表。
像這種情況,如果對於 image 的資料結構、邏輯處理在兩邊都相差無幾,就可以考慮應用多型態關聯,將表單合成一張。
可以看到 images 資料表有兩個用於參照的欄位
這麼做的好處是可以合一管理 image 的處理邏輯,像是 CRUD 的操作,反過來說就是耦合提高了,變動邏輯的時候要十分小心會不會影響到某一邊的運作。所以在應用多型態關聯的時候一定要考慮清楚合併到一塊的模型是否真的能夠共用邏輯,未來會不會有很多的改動。
多型態關聯的應用情境相對少,這邊就直接借用官方的範例做說明了。
以上面 users , posts , images 的架構,對應的 Eloquent Model 會長這樣
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Image extends Model
{
public function imageable()
{
return $this->morphTo();
}
}
class Post extends Model
{
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
}
class User extends Model
{
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
}
可以看到新出現的 morphOne,morphTo 方法,可以想成是 hasOne , belongsTo 的多型態版本。
morphOne 跟 hasOne 相比可以看到在目標類別之後多了一個 name 參數 imageable。
Eloquent 會根據這個 name 預設目標表單上的查詢欄位,以 imageable 為例的話就是查詢 imageable_type 跟 imageable_id 。
也可以自訂目標的查詢欄位名稱
// morphOne(目標表單名稱,多形名稱,目標的型別欄位名稱,目標的外鍵欄位名稱,自己的關聯鍵)
$this->morphOne(Image::class, 'imageable','imageable_type','imageable_id','id');
imageable_id 會用於儲存關聯的 id 值,就不用多介紹了,至於 imageable_type 則會儲存 User 跟 Post 的類別名稱,具體來說就是 'App\Models\User' 跟 'App\Models\Post' 這兩種字串。
使用 morphTo 要注意函式的名稱。
public function imageable()
{
return $this->morphTo();
}
如果沒有帶入參數的話預設會用函式的名稱產出預設名稱,像這裡的函式名稱是 imageable ,那查詢時就會以型別欄位 imageable_type 查對應的資料表,以及鍵值欄位 imageable_id 查資料。
可以自訂用來產生預設名稱的 name
$this->morphTo('imageable');
或是乾脆自訂查詢欄位的名稱
// morphOne(多形名稱,型別欄位名稱,外鍵欄位名稱)
$this->morphTo('imageable','imageable_type','imageable_id');
關聯定義好之後使用方法就跟普通的 hasOne , belongsTo 相同了。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Image extends Model
{
public function imageable()
{
return $this->morphTo();
}
}
class Post extends Model
{
public function image()
{
return $this->morphMany(Image::class, 'imageable');
}
}
class User extends Model
{
public function image()
{
return $this->morphMany(Image::class, 'imageable');
}
}
跟一對一的關聯差不多,只差在 morphOne 改成 morphMany ,查詢出來的資料不是一筆而是一組。
將官方的範例畫成圖比較好理解
跟普通的多對多關聯相比,中介表單成了多型態的樣子。
多型的一方,以 post 為例,會用 morphToMany 方法。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* Get all of the tags for the post.
*/
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
morphToMany 就想成是 belongsToMany 的多型態版,跟 morphMany 一樣要帶上多型的名稱參數才能查資料。
// morphOne(目標表單名稱,多形名稱)
$this->morphToMany(Tag::class, 'taggable');
又像 belongsToMany 一樣要自訂名稱的話會變成超長一串。
// morphToMany(目標表單名稱,多形名稱,中介表單名稱,中介表單上參照自己的外鍵,中介表單上參照目標的外鍵,目標的關聯鍵,自己的關聯鍵)
$this->morphToMany(Tag::class, 'taggable','taggables','taggable_id','tag_id','id','id');
反向的關聯就用 morphedByMany 方法。一樣要帶上多型的名稱。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
/**
* Get all of the posts that are assigned this tag.
*/
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
/**
* Get all of the videos that are assigned this tag.
*/
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
}
自訂也是超長一串
// morphedByMany(目標表單名稱,多形名稱,中介表單名稱,中介表單上參照目標的外鍵,中介表單上參照自己的外鍵,自己的關聯鍵,目標的關聯鍵)
$this->morphedByMany(Post::class, 'taggable','taggables','taggable_id','tag_id','id','id');